package com.ccx.demo.business.common.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectResult;
import com.ccx.demo.business.common.vo.FileInfo;
import com.ccx.demo.config.AliOssConfiguration;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.utils.enums.Code;
import com.utils.util.FPath;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.channels.SeekableByteChannel;
import java.util.List;
import java.util.Optional;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * 阿里云 oss 读写服务接口
 *
 * @author 谢长春 2020-11-25
 */
@Primary
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(value = "spring.app.files.type", havingValue = "alioss")
public class AliOssFileService implements IFileService {
    private final AliOssConfiguration aliOssConfiguration;
    private final OSS aliOssClient;

    @SneakyThrows
    @Override
    public <T extends FileInfo> T write(final MultipartFile uploadFile, final T fileInfo, final boolean replace) {
        if (log.isInfoEnabled()) {
            log.info("上传文件路径【{}】：{}{}", fileInfo.getName(), aliOssConfiguration.getServer(), fileInfo.getPath());
        }
        Code.A00001.assertNonBlank(fileInfo.getPath(), "必须指定存储路径");
        if (!replace) {
            final boolean fileExists = aliOssClient.doesObjectExist(aliOssConfiguration.getBucketName(), fileInfo.getPath());
            if (fileExists) { // 文件已存在，不重复上传文件
                return fileInfo;
            }
        }
        @Cleanup final InputStream inputStream = uploadFile.getInputStream();
        final ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType(uploadFile.getContentType());
        metadata.setContentLength(inputStream.available());
        // 上传文件到存储桶
        final PutObjectResult result = aliOssClient.putObject(
                aliOssConfiguration.getBucketName(),
                fileInfo.getPath(),
                inputStream,
                metadata
        );
        if (log.isDebugEnabled()) {
            log.info("versionId={}, etag={}, requestId={}"
                    , result.getVersionId()
                    , result.getETag()
                    , result.getRequestId()
            );
        }
        return fileInfo;
    }

    @SneakyThrows
    @Override
    public <T extends FileInfo> T write(final byte[] bytes, final T fileInfo, final boolean replace) {
        @Cleanup final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        if (log.isInfoEnabled()) {
            log.info("上传文件路径【{}】：{}{}", fileInfo.getName(), aliOssConfiguration.getServer(), fileInfo.getPath());
        }
        Code.A00001.assertNonBlank(fileInfo.getPath(), "必须指定存储路径");
        if (!replace) {
            final boolean fileExists = aliOssClient.doesObjectExist(aliOssConfiguration.getBucketName(), fileInfo.getPath());
            if (fileExists) { // 文件已存在，不重复上传文件
                return fileInfo;
            }
        }
        final ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(byteArrayInputStream.available());
        metadata.setContentType(fileInfo.getContentType());
        // 上传文件到存储桶
        final PutObjectResult result = aliOssClient.putObject(
                aliOssConfiguration.getBucketName(),
                fileInfo.getPath(),
                byteArrayInputStream,
                metadata
        );
        if (log.isDebugEnabled()) {
            log.info("versionId={}, etag={}, requestId={}"
                    , result.getVersionId()
                    , result.getETag()
                    , result.getRequestId()
            );
        }
        return fileInfo;
    }

    @SneakyThrows
    @Override
    public <T extends FileInfo> T write(final File file, final T fileInfo, final boolean replace) {
        if (log.isInfoEnabled()) {
            log.info("上传文件路径【{}】：{}{}", fileInfo.getName(), aliOssConfiguration.getServer(), fileInfo.getPath());
        }
        Code.A00001.assertNonBlank(fileInfo.getPath(), "必须指定存储路径");
        if (!replace) {
            final boolean fileExists = aliOssClient.doesObjectExist(aliOssConfiguration.getBucketName(), fileInfo.getPath());
            if (fileExists) { // 文件已存在，不重复上传文件
                return fileInfo;
            }
        }
        @Cleanup final FileInputStream fileInputStream = new FileInputStream(file);
        @Cleanup final BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        final ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(bufferedInputStream.available());
        metadata.setContentType(fileInfo.getContentType());
        // 上传文件到存储桶
        final PutObjectResult result = aliOssClient.putObject(
                aliOssConfiguration.getBucketName(),
                fileInfo.getPath(),
                bufferedInputStream,
                metadata
        );
        if (log.isDebugEnabled()) {
            log.info("versionId={}, etag={}, requestId={}"
                    , result.getVersionId()
                    , result.getETag()
                    , result.getRequestId()
            );
        }
        return fileInfo;
    }

    @SneakyThrows
    @Override
    public Optional<String> readString(final FileInfo file) {
        @Cleanup final OSSObject object = aliOssClient.getObject(aliOssConfiguration.getBucketName(), file.getPath());
        @Cleanup final InputStream inputStream = object.getObjectContent();
        @Cleanup final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, UTF_8);
        return Optional.of(CharStreams.toString(inputStreamReader));
    }

    @Override
    public <T extends FileInfo, R> Optional<R> readJson(final T file, final TypeReference<R> typeReference) {
        return readString(file).map(jsonText -> JSON.parseObject(jsonText, typeReference));
    }

    @Override
    public <T extends FileInfo, R> Optional<R> readJsonObject(final T file, final Class<R> clazz) {
        return readString(file).map(jsonText -> JSON.parseObject(jsonText, clazz));
    }

    @Override
    public <T extends FileInfo, R> Optional<List<R>> readJsonArray(final T file, final Class<R> clazz) {
        return readString(file).map(jsonText -> JSON.parseArray(jsonText, clazz));
    }

    @SneakyThrows
    @Override
    public byte[] readBytes(final FileInfo file) {
        @Cleanup final OSSObject object = aliOssClient.getObject(aliOssConfiguration.getBucketName(), file.getPath());
        @Cleanup final InputStream inputStream = object.getObjectContent();
        return ByteStreams.toByteArray(inputStream);
    }

    @SneakyThrows
    @Override
    public BufferedReader readStream(final FileInfo file) {
        @Cleanup final OSSObject object = aliOssClient.getObject(aliOssConfiguration.getBucketName(), file.getPath());
        @Cleanup final InputStream inputStream = object.getObjectContent();
        @Cleanup final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, UTF_8);
        return new BufferedReader(inputStreamReader);
    }

    @SneakyThrows
    @Override
    public InputStream readInputStream(final FileInfo file) {
        @Cleanup final OSSObject object = aliOssClient.getObject(aliOssConfiguration.getBucketName(), file.getPath());
        @Cleanup final InputStream inputStream = object.getObjectContent();
        return inputStream;
    }

    @SneakyThrows
    @Override
    public SeekableByteChannel readChannel(final FileInfo file) {
        throw new Exception("alioss 暂不支持 SeekableByteChannel");
    }

    @SneakyThrows
    @Override
    public void copy(final FileInfo file, final String absolutePath) {
        @Cleanup final FileOutputStream fileOutputStream = new FileOutputStream(absolutePath);
        ByteStreams.copy(readInputStream(file), fileOutputStream);
        FPath.of(absolutePath).chmod(644);
    }

    @SneakyThrows
    @Override
    public void copy(final FileInfo file, final OutputStream outputStream) {
        ByteStreams.copy(readInputStream(file), outputStream);
    }
}
